﻿using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using Scaffold;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace GridCut
{
	public interface IImageTransformer
	{
		Mat Transform(IInputArray src);
	}

	public class ImageProcessor: IImageTransformer
	{
		public Calibrator calibrator;
		public double VectorAngle(Tuple<Point, Point> vec)
		{
			return Math.Atan2((vec.Item2.Y - vec.Item1.Y), (vec.Item2.X - vec.Item1.X));
		}
		public ImageProcessor(int w, int h, string folder, string cfg)
		{
			calibrator = new Calibrator(w, h);
			calibrator.Calibrate(folder, cfg, false);
		}
		public ImageProcessor()
		{
		}
		public VectorOfVectorOfPoint ExtractContour(IInputArray input)
		{
			Mat thresed = new Mat();
			CvInvoke.Threshold(input, thresed, 128, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
			Mat hie = new Mat();
			VectorOfVectorOfPoint tmp = new VectorOfVectorOfPoint();
			CvInvoke.FindContours(thresed, tmp, hie, Emgu.CV.CvEnum.RetrType.List, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxTc89Kcos);
			var contours = new VectorOfVectorOfPoint(tmp.Size);
			for (int i = 0; i < tmp.Size; i++)
			{
				CvInvoke.ApproxPolyDP(tmp[i], contours[i], 5, true);
			}
			return contours;
		}
		public List<Point> GenHoriCuttingPath(VectorOfPoint contourVec)
		{
			Rectangle boundingbox = CvInvoke.BoundingRectangle(contourVec);
			var contour = contourVec.ToArray().ToList();
			List<Tuple<Point, Point>> q = contour.Take(contour.Count - 1).Select((x, ind) => new Tuple<Point, Point>(x, contour[ind + 1])).ToList();
			q.Add(new Tuple<Point, Point>(contour[contour.Count - 1], contour[0]));
			var up = q.Where(x => VectorAngle(x) >= 3 * Math.PI / 4 || VectorAngle(x) <= -3 * Math.PI / 4);
			var dn = q.Where(x => VectorAngle(x) > -Math.PI / 4 && VectorAngle(x) < Math.PI / 4);
			List<Point> path = new List<Point>();
			int upY = up.Max(x => Math.Max(x.Item1.Y, x.Item2.Y));
			int dnY = dn.Min(x => Math.Min(x.Item1.Y, x.Item2.Y));
			int hei = Math.Min(dnY - upY, Config.config.GetCuttingHeightIncludingKnf());
			int U = upY + (dnY - upY - hei) / 2;
			int D = U + hei;
			dnY = Math.Min(dnY, upY + Config.config.GetCuttingHeightIncludingKnf());
			path.Add(new Point(boundingbox.Left - Config.config.GetExtraXPX(), U));
			path.Add(new Point(boundingbox.Right + Config.config.GetExtraXPX(), U));
			path.Add(new Point(boundingbox.Left - Config.config.GetExtraXPX(), D));
			path.Add(new Point(boundingbox.Right + Config.config.GetExtraXPX(), D));
			return path;
		}
		public List<Point> GenHoriCuttingPath(List<Rectangle> rects)
		{
			Console.WriteLine($"共有{rects.Count}个矩形, 左右额外长度 {Config.config.GetExtraXPX()}像素 = {Config.config.ExtraX}mm。[1px={Config.config.PXpMM}mm]");
			int L = rects.Min(x => x.Left) - Config.config.GetExtraXPX();
			int R = rects.Max(x => x.Right) + Config.config.GetExtraXPX();
			List<Point> ans = new List<Point>();
			foreach (var rect in rects)
			{
				int hei = Math.Min(Config.config.GetCuttingHeightIncludingKnf(), rect.Height);
				int y = rect.Top + (rect.Height - hei) / 2;
				ans.Add(new Point(L, y));
				ans.Add(new Point(R, y));
				ans.Add(new Point(L, y + hei));
				ans.Add(new Point(R, y + hei));
				Console.WriteLine($" * 路径左上角像素位置({L}, {y}); 宽度(含额外长度):{R-L}; (含刀片)设置高度:{Config.config.GetCuttingHeightIncludingKnf()}; 手画高度: {rect.Height}. 实际切割高度: {hei}");
			}
			return ans;
		}

		public Point AngleOffset(PointF p, float ang)
		{
			return new Point((int)(p.X + 10 * Math.Cos(ang)), (int)(p.Y + 10 * Math.Sin(ang)));
		}
		public List<Point> GenVertCuttingPath(VectorOfVectorOfPoint contours, out int totalCount)
		{
			int U = int.MinValue, D = int.MinValue, L = int.MaxValue, R = int.MinValue;
			totalCount = 0;
			for (int i = 0; i < contours.Size; i++)
			{
				var contour = contours[i].ToArray().ToList();
				R = Math.Max(R, contour.Max(x => x.X));
				L = Math.Min(L, contour.Min(x => x.X));
				List<Tuple<Point, Point>> q = contour.Take(contour.Count - 1).Select((x, ind) => new Tuple<Point, Point>(x, contour[ind + 1])).ToList();
				q.Add(new Tuple<Point, Point>(contour[contour.Count - 1], contour[0]));
				var up = q.Where(x => VectorAngle(x) >= 3 * Math.PI / 4 || VectorAngle(x) <= -3 * Math.PI / 4);
				var dn = q.Where(x => VectorAngle(x) > -Math.PI / 4 && VectorAngle(x) < Math.PI / 4);
				U = Math.Max(U, up.Max(x => Math.Max(x.Item1.Y, x.Item2.Y)));
				D = Math.Max(D, dn.Min(x => Math.Min(x.Item1.Y, x.Item2.Y)));
				totalCount += (int)Math.Floor((double)((D - U) / Config.config.GetCuttingWidthIncludeingKnf()));
			}
			int N = (int)Math.Floor((double)((D - U) / Config.config.GetCuttingWidthIncludeingKnf()));
			L -= Config.config.GetExtraXPX();
			R += Config.config.GetExtraXPX();
			List<Point> path = new List<Point>(new Point[] { new Point(L, U), new Point(R, U) });
			
			for (int i = 0; i < N; i++)
			{
				path.Add(new Point(L, U + Config.config.GetCuttingWidthIncludeingKnf() * (i + 1)));
				path.Add(new Point(R, U + Config.config.GetCuttingWidthIncludeingKnf() * (i + 1)));
			}
			return path;
		}

		/// <summary>
		/// 计算逆时针转90度后的切割路径。输入是旋转前矩形框与原图大小
		/// </summary>
		/// <param name="rects">旋转前的矩形</param>
		/// <param name="oriSz">未旋转的图像大小</param>
		/// <returns>旋转90度后的切割路径</returns>
		public List<Point> GenVertCuttingPath(List<Rectangle> rects, Size oriSz)
		{
			var transposed = rects.Select(x => new Rectangle(new Point(x.Y, oriSz.Width-(x.X+x.Width)), new Size(x.Height, x.Width))).ToList();
			int U = transposed.Max(x => x.Top);
			int D = transposed.Max(x => x.Bottom);
			int L = transposed.Min(x => x.Left) - Config.config.GetExtraXPX();
			int R = transposed.Max(x => x.Right) + Config.config.GetExtraXPX();
			int N = (int)Math.Floor((double)(D-U)/Config.config.GetCuttingWidthIncludeingKnf());
			List<Point> path = new List<Point> { new Point(L,U), new Point(R, U) };
			Console.WriteLine($"共切{N}次, 左右额外长度 {Config.config.GetExtraXPX()}像素 = {Config.config.ExtraX}mm。[1px={Config.config.PXpMM}mm]");
			for (int i = 0; i < N; i++)
			{
				path.Add(new Point(L, U + Config.config.GetCuttingWidthIncludeingKnf() * (i + 1)));
				path.Add(new Point(R, U + Config.config.GetCuttingWidthIncludeingKnf() * (i + 1)));
				Console.WriteLine($"第{i+1}次切割, 路径Y方向(旋转后)像素位置: {U + Config.config.GetCuttingWidthIncludeingKnf() * (i + 1)}");
			}
			return path;
		}

		public Mat WarpPerspective(IInputArray src)
		{
			var shape = new Size(Config.config.ImgWid, Config.config.ImgHei);
			Mat output = new Mat();
			Mat m = new Mat();
			List<PointF> q = new List<PointF>() { new PointF(0,0), new PointF(shape.Width, 0),
												new PointF(0, shape.Height), new PointF(shape.Width, shape.Height) };
			List<PointF> p = new List<PointF>{
				new PointF(Config.config.Pt1.X, Config.config.Pt1.Y), 
				new PointF(Config.config.Pt2.X, Config.config.Pt2.Y), 
				new PointF(Config.config.Pt3.X, Config.config.Pt3.Y), 
				new PointF(Config.config.Pt4.X, Config.config.Pt4.Y)
			};

			m = CvInvoke.GetPerspectiveTransform(new VectorOfPointF(p.ToArray()), new VectorOfPointF(q.ToArray()));
			CvInvoke.WarpPerspective(src, output, m, shape);
			return output;
		}

		public Mat Rotate90Clk(IInputArray src)
		{
			Mat m = new Mat();
			CvInvoke.Transpose(src, m);
			CvInvoke.Flip(m, m,  Emgu.CV.CvEnum.FlipType.Horizontal);
			return m;
		}

		public Mat Rotate90AntClk(IInputArray src)
		{
			Mat m = new Mat();
			CvInvoke.Transpose(src, m);
			CvInvoke.Flip(m, m,  Emgu.CV.CvEnum.FlipType.Vertical);
			return m;
		}

		public Mat Transform(IInputArray src)
		{
			Mat t = new Mat();
			calibrator.Undistort(src, t);
			return WarpPerspective(t);
		}
	}
}
